package org.mzx.shortLink.work.mq.consumer;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mzx.shortLink.work.common.constant.rabbitMq.rabbitMqShortLinkStatusDto;
import org.mzx.shortLink.work.domain.entity.*;
import org.mzx.shortLink.work.domain.mapper.*;
import org.mzx.shortLink.work.service.TLinkAccessStatsService;
import org.mzx.shortLink.work.service.TLinkGotoService;
import org.mzx.shortLink.work.service.TLinkLocaleStatsService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.sql.Date;
import java.util.HashMap;
import java.util.Map;

import static org.mzx.shortLink.work.common.enums.RedisData.*;
import static org.mzx.shortLink.work.common.enums.rabbitMqData.*;
@Slf4j
@RequiredArgsConstructor
@Component
public class shortLinkStatusSaveConsumer {

    private final RedissonClient redissonClient;

    private final TLinkGotoService gotoService;

    private final StringRedisTemplate stringRedisTemplate;



    private final TLinkAccessStatsService tLinkAccessStatsService;

    @Value("${short-link.statsLocaleAmapKey}")
    public String statsLocaleAmapKey;

    private final TLinkLocaleStatsService linkLocaleStatsService;

    private final TLinkOsStatsMapper linkOsStatsMapper;

    private final TLinkBrowserStatsMapper linkBrowserStatsMapper;

    private final TLinkDeviceStatsMapper linkDeviceStatsMapper;

    private final TLinkNetworkStatsMapper linkNetworkStatsMapper;

    private final TLinkAccessLogsMapper linkAccessLogsMapper;


    /**
     * 整三个消费者去监听这个队列
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = SHORT_LINK_STATUS_QUEUE,durable = "true"),
            exchange = @Exchange(name = SHORT_LINK_STATUS_EXCHANGE,type = ExchangeTypes.DIRECT),
            key = {SHORT_LINK_STATUS_KEY}
    ))
    private void getStatusMessage1(rabbitMqShortLinkStatusDto statusDto){

        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(SHORT_LINK_UPDATE_LOCK_KEY + statusDto.getFullUrl());
        RLock rLock = readWriteLock.readLock();
        //获取读锁，读锁可以无线获取
        boolean tryLock = rLock.tryLock();
        if (tryLock) {
            try {
                saveStatus(statusDto);
            }catch (Exception e){
                log.debug(e.getMessage());
            }finally {
                rLock.unlock();
            }
        }

    }
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = SHORT_LINK_STATUS_QUEUE,durable = "true"),
            exchange = @Exchange(name = SHORT_LINK_STATUS_EXCHANGE,type = ExchangeTypes.DIRECT),
            key = {SHORT_LINK_STATUS_KEY}
    ))
    private void getStatusMessage2(rabbitMqShortLinkStatusDto statusDto){


        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(SHORT_LINK_UPDATE_LOCK_KEY + statusDto.getFullUrl());
        RLock rLock = readWriteLock.readLock();
        //获取读锁，读锁可以无线获取
        boolean tryLock = rLock.tryLock();
        if (tryLock) {
            try {
                saveStatus(statusDto);
            }catch (Exception e){
                log.debug(e.getMessage());
            }finally {
                rLock.unlock();
            }
        }
    }



    private void saveStatus(rabbitMqShortLinkStatusDto statusDto) {


        Integer year = statusDto.getYear();
        Date date = statusDto.getDate();
        Integer day = statusDto.getDay();
        Integer month = statusDto.getMonth();
        Integer hour = statusDto.getHour();
        Integer weekDay = statusDto.getWeekDay();
        String fullUrl = statusDto.getFullUrl();
        String cookieValue = statusDto.getCookieValue();

        String ip = statusDto.getIp();

        //利用独特的数据结构HyperLogLog来存储短链接日活
        stringRedisTemplate.opsForHyperLogLog().add(SHORT_LINK_STATS + year + ":" + month + ":" + day + ":" + fullUrl, cookieValue);
        stringRedisTemplate.opsForHyperLogLog().add(SHORT_LINK_STATS_IP + year + ":" + month + ":" + day + ":" + fullUrl, cookieValue);
        TLinkAccessStats tLinkAccessStats = tLinkAccessStatsService.lambdaQuery().eq(TLinkAccessStats::getFullShortUrl, fullUrl)
                .eq(TLinkAccessStats::getDate, date)
                .eq(TLinkAccessStats::getHour, hour)
                .one();
        //如果短链接为空，那么改插入
        TLinkGoto tLinkGoto = gotoService.lambdaQuery()
                .eq(TLinkGoto::getFullShortUrl, fullUrl)
                .one();
        if (tLinkAccessStats != null) {
            tLinkAccessStatsService.lambdaUpdate().eq(TLinkAccessStats::getFullShortUrl, fullUrl)
                    .eq(TLinkAccessStats::getDate, date)
                    .eq(TLinkAccessStats::getHour, hour)
                    .set(TLinkAccessStats::getUv, stringRedisTemplate.opsForHyperLogLog().size(SHORT_LINK_STATS + year + ":" + month + ":" + day + ":" + fullUrl, cookieValue))
                    .set(TLinkAccessStats::getUip, stringRedisTemplate.opsForHyperLogLog().size(SHORT_LINK_STATS_IP + year + ":" + month + ":" + day + ":" + fullUrl, ip))
                    .set(TLinkAccessStats::getPv, tLinkAccessStats.getPv() + 1)
                    .update();
        } else {

            TLinkAccessStats linkAccessStats = TLinkAccessStats.builder()
                    .pv(1)
                    .hour(hour)
                    .uip(1)
                    .uv(1)
                    .gid(tLinkGoto.getGid())
                    .fullShortUrl(fullUrl)
                    .weekday(weekDay)
                    .date(date)
                    .build();

            tLinkAccessStatsService.save(linkAccessStats);
        }
        //解析ip得到城市省份之类的地理位置
        Map<String, Object> localeParamMap = new HashMap<>();
        localeParamMap.put("key", statsLocaleAmapKey);
        localeParamMap.put("ip", ip);
        String localeResultStr = HttpUtil.get("https://restapi.amap.com/v3/ip?parameters", localeParamMap);
        JSONObject localeResultObj = JSON.parseObject(localeResultStr);
        String infoCode = localeResultObj.getString("infocode");
        if (StrUtil.isNotBlank(infoCode) && StrUtil.equals(infoCode, "10000")) {

            String province = localeResultObj.getString("province").equals("[]")?"未知":localeResultObj.getString("province");
            String city = localeResultObj.getString("city").equals("[]")?"未知":localeResultObj.getString("city");
            String adcode = localeResultObj.getString("adcode").equals("[]")?"未知":localeResultObj.getString("adcode");

            TLinkLocaleStats stats = linkLocaleStatsService.lambdaQuery().eq(TLinkLocaleStats::getFullShortUrl, fullUrl)
                    .eq(TLinkLocaleStats::getCity, city)
                    .eq(TLinkLocaleStats::getDate, date)
                    .eq(TLinkLocaleStats::getProvince, province)
                    .eq(TLinkLocaleStats::getAdcode, adcode)
                    .one();
            //检查数据库中是否存在某日期的地理位置信息，如果有，则改为更新，没有改为新增
            if (stats != null) {
                linkLocaleStatsService.lambdaUpdate().eq(TLinkLocaleStats::getFullShortUrl, fullUrl)
                        .eq(TLinkLocaleStats::getCity, city)
                        .eq(TLinkLocaleStats::getDate, date)
                        .eq(TLinkLocaleStats::getProvince, province)
                        .eq(TLinkLocaleStats::getAdcode, adcode)
                        .set(TLinkLocaleStats::getCnt, stats.getCnt() + 1)
                        .update();
            } else {

                TLinkLocaleStats linkLocaleStatsDO = TLinkLocaleStats.builder()
                        .province(province)
                        .city(city)
                        .adcode(adcode)
                        .cnt(1)
                        .fullShortUrl(fullUrl)
                        .country("中国")
                        .gid(tLinkGoto.getGid())
                        .date(date)
                        .build();
                linkLocaleStatsService.save(linkLocaleStatsDO);
            }
        }

        //添加操作系统记录
        String os = statusDto.getOs();
        TLinkOsStats linkOsStatsDO = TLinkOsStats.builder()
                .os(os)
                .cnt(1)
                .gid(tLinkGoto.getGid())
                .fullShortUrl(fullUrl)
                .date(date)
                .build();
        linkOsStatsMapper.shortLinkOsState(linkOsStatsDO);

        //添加浏览器记录
        String browser = statusDto.getBrowser();
        TLinkBrowserStats linkBrowserStatsDO = TLinkBrowserStats.builder()
                .browser(browser)
                .cnt(1)
                .gid(tLinkGoto.getGid())
                .fullShortUrl(fullUrl)
                .date(date)
                .build();
        linkBrowserStatsMapper.shortLinkBrowserState(linkBrowserStatsDO);

        //添加访问设备信息
        String device = statusDto.getDevice();
        TLinkDeviceStats linkDeviceStatsDO = TLinkDeviceStats.builder()
                .device(device)
                .cnt(1)
                .gid(tLinkGoto.getGid())
                .fullShortUrl(fullUrl)
                .date(date)
                .build();
        linkDeviceStatsMapper.shortLinkDeviceState(linkDeviceStatsDO);

        //添加访问网络信息
        TLinkNetworkStats linkNetworkStatsDO = TLinkNetworkStats.builder()
                .network(statusDto.getNetWork())
                .cnt(1)
                .gid(tLinkGoto.getGid())
                .fullShortUrl(fullUrl)
                .date(date)
                .build();
        linkNetworkStatsMapper.shortLinkNetworkState(linkNetworkStatsDO);

        //添加高频ip，同时也是添加浏览日志
        TLinkAccessLogs linkAccessLogsDO = TLinkAccessLogs.builder()
                .user(cookieValue)
                .ip(ip)
                .browser(browser)
                .os(os)
                .gid(tLinkGoto.getGid())
                .fullShortUrl(fullUrl)
                .build();
        linkAccessLogsMapper.insert(linkAccessLogsDO);
    }


}
